/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.common.dataset;

import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;

import org.telosys.common.TelosysRuntimeException;
import org.telosys.common.data.FieldComparer;

/**
 * The DataSet class <br>
 * <br>
 * A DataSet is set of DataRows whith the same number of cells in each row <br>
 * It's a kind of grid that can be accessed by row or by cell coordinates (X,Y) <br>
 * A DataSet can be sorted by column <br>
 * <br>
 * 
 * @author Laurent GUERIN
 *  
 */
public class DataSet implements Serializable, Iterable<DataRow>
{
	private static final long serialVersionUID = 1L;

	//private final static String DUMMY_OBJECT = "";
	private final static DataRow DUMMY_OBJECT = new DataRow(0);
    
    private final static int     COL_COUNT_UNDEFINED = 0;
    
    //-----------------------------------------------------------------------------
    //--- PRIVATE ATTRIBUTES
    //-----------------------------------------------------------------------------
    private LinkedList<DataRow> _rows      = null;

    private int                 _iRowCount = 0;

    private int                 _iColCount = COL_COUNT_UNDEFINED ;
    
    //-----------------------------------------------------------------------------
    private boolean    _bSorted        = false;

    private int        _iSortedByCol   = -1;

    private boolean    _bSortOrder     = FieldComparer.ASC;


    //-----------------------------------------------------------------------------
    /**
     * Constructs a new void DataSet with an undefined number of columns <br>
     * The number of columns will be defined by the first row added in the DataSet
     */
    public DataSet()
    {
        super();
        _iColCount = COL_COUNT_UNDEFINED ;
        init();
    }

    /**
     * Constructs a new void DataSet with the number of columns
     * @param numberOfColumns
     */
    public DataSet(int numberOfColumns)
    {
        super();
        _iColCount = numberOfColumns ;
        init();
    }
    
    
    /**
     * Data set initialization, must be called by subclasses before each load
     */
    private void init()
    {
        _rows = new LinkedList<DataRow>(); 
        _rows.add(DUMMY_OBJECT); // DUMMY OBJECT at Position 0 ( for "base 1" index )
        _iRowCount = 0;
    }

    /**
     * Data set initialization, must be called by subclasses before each load
     */
    public void clear()
    {
        if ( _rows != null )
        {
            _rows.clear(); // just for GC 
        }
        init();
    }


    //-----------------------------------------------------------------------------
    // PRIVATE METHOD(S)
    //-----------------------------------------------------------------------------
    /**
     * Returns the boolean order flag according to the given order in string format
     * @param sOrder ( "asc" or "desc" )
     * @return
     */
    private boolean getSortOrderFlag(String sOrder)
    {
        if (sOrder == null)
        {
            throw new TelosysRuntimeException("DataSet.sort : order parameter is 'null' ");
        }
        String sOrderUC = sOrder.trim().toUpperCase();
        if (sOrderUC.startsWith("ASC"))
        {
            return FieldComparer.ASC;
        }
        else if (sOrderUC.startsWith("DESC"))
        {
            return FieldComparer.DESC;
        }
        else
        {
            throw new TelosysRuntimeException("DataSet.sort : invalid order parameter '" + sOrder + "' ( 'ASC' or 'DESC' expected )");
        }
    }

    //-----------------------------------------------------------------------------
    /**
     * Sorts the list according to the given parameters
     * @param sFieldName : field to use as sort criteria
     * @param bOrder : sort order flag ( ASC / DESC )
     * @param bFlagIgnoreCase : ignore case flag 
     */
    private void sortList(int iCol, boolean bOrder, boolean bFlagIgnoreCase)
    {
        //--- 1) Remove the DUMMY_OBJECT ( for "base 1" index )
        _rows.remove(0); 
        //--- 2) SORT the list (with only DataRows objects ) 
        Collections.sort(_rows, new DataRowComparer(iCol, bOrder, bFlagIgnoreCase));
        //--- 3) Restore the DUMMY OBJECT at Position 0 ( for "base 1" index )
        _rows.addFirst(DUMMY_OBJECT); 

        _bSorted = true;
        _iSortedByCol = iCol;
        _bSortOrder = bOrder;
    }
    
    //-----------------------------------------------------------------------------
    //--- PROTECTED METHODS
    //-----------------------------------------------------------------------------
    private void checkRowNotNull(DataRow dataRow)
    {
        if ( null == dataRow )
        {
            throw new TelosysRuntimeException("DataSet error : data row is null");
        }
    }
    private void checkRowSize(DataRow dataRow)
    {
        //--- Is the column count compatible with the DataSet ?
        if ( dataRow.getSize() != _iColCount )
        {
            throw new TelosysRuntimeException("DataSet error : invalid data row size (" 
                    + dataRow.getSize() + " column(s), " + _iColCount + " expected)");
        }
    }
    //-----------------------------------------------------------------------------
    /**
     * Replaces the DataRow at the given position by a new one
     * @param iRow index of row to replace ( 1 to N )
     * @param dataRow the DataRow to be stored at the specified position
     * @return the DataRow previously at the specified position
     */
    public DataRow replaceRow(int iRow, DataRow dataRow)
    {
        checkRowNotNull(dataRow);
        if ( _rows == null )
        {
            throw new TelosysRuntimeException("DataSet replaceRow("+iRow+",DataRow) : the DataSet is not initialized !");
        }
        //--- Is the row position valid ?
        if (iRow < 1 || iRow > _iRowCount)
        {
            throw new TelosysRuntimeException("DataSet replaceRow("+iRow+",DataRow) : invalid row position !");
        }
//        if ( dataRow == null )
//        {
//            throw new TelosysRuntimeException("DataSet replaceRow("+iRow+",null) : data row is null !");
//        }
//        //--- Is the column count compatible with the DataSet ?
//        if ( dataRow.getSize() != _iColCount )
//        {
//            throw new TelosysRuntimeException("DataSet replaceRow("+iRow+",DataRow) : invalid data row size (" 
//                    + dataRow.getSize() + " column(s) : " + _iColCount + " expected )");
//        }
        checkRowSize(dataRow);
        return (DataRow) _rows.set(iRow, dataRow);
    }
    //-----------------------------------------------------------------------------
    /**
     * Adds a DataRow at the end of the DataSet
     * @param dataRow the DataRow to add
     * @return
     */
    public int addRow(DataRow dataRow)
    {
        checkRowNotNull(dataRow);
//        if ( _rows == null )
//        {
//            throw new TelosysRuntimeException("DataSet addRow(DataRow) : the DataSet is not initialized !");
//        }
        //--- Check row size if not the first row 
        if ( _iColCount == COL_COUNT_UNDEFINED )
        {
            _iColCount = dataRow.getSize(); // The first datarow defines the number of columns
        }
        else 
        {
            checkRowSize(dataRow);
//        	if ( _iColCount != dataRow.getSize() ) 
//        	{
//                throw new TelosysRuntimeException("DataSet addRow(DataRow) : invalid DataRow ("+ _iColCount + " columns expected)" );
//        	}
        }
        //--- Add the row
        _rows.add(dataRow);
        _iRowCount++;
        return _iRowCount;
    }
    //-----------------------------------------------------------------------------
//    /**
//     * Adds a new void DataRow at the end of the DataSet
//     * @return
//     */
//    protected int addRow()
//    {
//        if ( _rows == null )
//        {
//            throw new TelosysRuntimeException("DataSet addRow() : the DataSet is not initialized !");
//        }
//        return addRow( new DataRow(_iColCount)) ;
//    }
    
    //-----------------------------------------------------------------------------
    //--- PUBLIC METHODS
    //-----------------------------------------------------------------------------
    /**
     * Returns the number of rows
     * 
     * @return
     */
    public int getRowCount()
    {
        return _iRowCount;
    }

    /**
     * Returns the number of columns
     * 
     * @return
     */
    public int getColCount()
    {
        return _iColCount ;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns 'true' if the DataSet is empty.
     * 
     * @return
     */
    public boolean isEmpty()
    {
        return (_iRowCount < 1 ? true : false);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns an iterator over the 'datarows' in this DataSet
     * 
     * @return
     */
    public Iterator<DataRow> iterator()
    {
        return new DataSetIterator(this);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the DataRow located at the given index 
     * @param iRow the row index ( 1 to N )
     * @return the DataRow found, or null if no row at this index 
     */
    public DataRow getDataRow(int iRow)
    {
        if (iRow < 1 || iRow > _iRowCount)
        {
            return null;
        }
        else
        {
            return (DataRow) _rows.get(iRow);
        }
    }


    //-----------------------------------------------------------------------------
    // Get Cell value with different types
    //-----------------------------------------------------------------------------
    /**
     * Returns the object located at the given cell coordinates 
     * @param iRow row index ( 1 to N )
     * @param iCol column index ( 1 to N )
     * @return instance found or null 
     * @throws TelosysRuntimeException if invalid column index 
     */
    public Object getObject(int iRow, int iCol)
    {
        DataRow dr = getDataRow(iRow);
        if (dr != null)
        {
            return dr.getObject(iCol);
        }
        return null;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the String located at the given cell coordinates
     * @param iRow row index ( 1 to N )
     * @param iCol column index ( 1 to N )
     * @return instance found or null 
     * @throws TelosysRuntimeException if invalid column index 
     */
    public String getString(int iRow, int iCol)
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getString(iCol) : null);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the String located at the given cell coordinates with a default value
     * @param iRow row index ( 1 to N )
     * @param iCol column index ( 1 to N )
     * @param sNullValue
     * @return instance found or default value 
     * @throws TelosysRuntimeException if invalid column index 
     */
    public String getString(int iRow, int iCol, String sNullValue)
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getString(iCol, sNullValue) : null);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the java.util.Date located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return instance found or null 
     * @throws TelosysRuntimeException if invalid column index 
     */
    public java.util.Date getDate(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getDate(iCol) : null);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the java.sql.Date located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return instance found or null 
     * @throws TelosysRuntimeException if invalid column index 
     */
    public java.sql.Date getSqlDate(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getSqlDate(iCol) : null);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the boolean value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or false if not found
     */
    public boolean getBoolean(int iRow, int iCol)
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getBoolean(iCol) : false);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the byte value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or 0 if not found
     */
    public byte getByte(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getByte(iCol) : 0);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the int value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or 0 if not found
     */
    public int getInt(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getInt(iCol) : 0);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the short value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or 0 if not found
     */
    public short getShort(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getShort(iCol) : 0);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the long value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or 0 if not found
     */
    public long getLong(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getLong(iCol) : 0);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the float value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or 0 if not found
     */
    public float getFloat(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getFloat(iCol) : 0);
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns the double value located at the given cell coordinates 
     * @param iRow iRow row index ( 1 to N )
     * @param iCol iCol column index ( 1 to N )
     * @return the value found, or 0 if not found
     */
    public double getDouble(int iRow, int iCol) 
    {
        DataRow dr = getDataRow(iRow);
        return (dr != null ? dr.getDouble(iCol) : 0);
    }
    //-----------------------------------------------------------------------------
    
    
    //-----------------------------------------------------------------------------
    //-----------------------------------------------------------------------------
    /**
     * Returns the column used as current sort criteria
     * 
     * @return : column number, or -1 if the DataSet is not sorted
     */
    public int getSortColumn()
    {
        if (_bSorted )
        {
            return _iSortedByCol;
        }
        return -1;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns true if the DataSet is currently sorted by the given column
     * @param iCol
     * @return
     */
    public boolean isSortedBy(int iCol)
    {
        if (_bSorted && (_iSortedByCol == iCol) )
        {
            return true;
        }
        return false;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns true if the DataSet is currently sorted in ascending order
     * @return
     */
    public boolean isSortedInAscendingOrder()
    {
        if (_bSorted && (_bSortOrder == FieldComparer.ASC))
        {
            return true;
        }
        return false;
    }

    //-----------------------------------------------------------------------------
    /**
     * Returns true if the DataSet is currently sorted in descending order
     * @return
     */
    public boolean isSortedInDescendingOrder()
    {
        if (_bSorted && (_bSortOrder == FieldComparer.DESC))
        {
            return true;
        }
        return false;
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the DataSet using the given column as criteria (case sensitive) in ascending order
     * @param iCol 
     */
    public void sort(int iCol)
    {
        sortList(iCol, FieldComparer.ASC, FieldComparer.DO_NOT_IGNORE_CASE);
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the DataSet using the given column as criteria (case sensitive) and the specified order ( ascending or descending )
     * @param iCol
     * @param sOrder ( "asc" or "desc" )
     */
    public void sort(int iCol, String sOrder)
    {
        boolean bOrder = getSortOrderFlag(sOrder);
        sortList(iCol, bOrder, FieldComparer.DO_NOT_IGNORE_CASE);
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the DataSet using the given column as criteria ( ignoring case ) in ascending order
     * @param iCol
     */
    public void sortIgnoreCase(int iCol)
    {
        sortList(iCol, FieldComparer.ASC, FieldComparer.IGNORE_CASE);
    }

    //-----------------------------------------------------------------------------
    /**
     * Sort the DataSet using the given field as criteria ( ignoring case ) <br>
     * and the specified order ( ascending or descending )
     * @param iCol
     * @param sOrder ( "asc" or "desc" )
     */
    public void sortIgnoreCase(int iCol, String sOrder)
    {
        boolean bOrder = getSortOrderFlag(sOrder);
        sortList(iCol, bOrder, FieldComparer.IGNORE_CASE);
    }
    
}